"""
PlumeSoft 量化代码库
作者：PlumeSoft
用途：贝叶斯选币炼丹炉
"""

import os

################################
# 软件版本信息
################################

app_name = "PlumeSoft 贝叶斯选币炼丹炉 - 量化小论坛专用版"
app_ver_num = "v1.8"
app_ver_date = "2024.05.26"
app_version = f"{app_ver_num} rel {app_ver_date}"
app_build = 10
app_url = "https://bbs.quantclass.cn/thread/38819"
app_upgrade = "https://gitee.com/plumesoft/upgrade/wikis/pages/wiki?wiki_title=coin_bys"

################################
# 路径相关参数
################################

# 返回当前文件路径
_ = os.path.abspath(os.path.dirname(__file__))
# 返回上一级目录作为根目录
root_path = os.path.abspath(os.path.join(_, "../../"))
# 炼丹数据和结果输出目录
data_path = os.path.join(root_path, "data", "选币炼丹")
os.makedirs(data_path, exist_ok=True)
pic_path = os.path.join(root_path, "data", "选币炼丹_回测")
os.makedirs(pic_path, exist_ok=True)

# 炼丹数据存放目录，支持使用以下宏：
# $STGNAME$：策略名称
# $DATE$：日期，格式为 YYYYMMDD
# $TIME$：时间，格式为 HHMMSS
# $BATCH-ID$：批量任务编号，格式为批量创建时间 YYYYMMDD$HHMMSS
# $ROUND-ID$：批量任务的轮次：格式为 NNN
# $ROLLING-ID$：滚动任务编号，格式为滚动创建时间 YYYYMMDD$HHMMSS
single_work_path = "单轮_未完成_$STGNAME$"
single_finished_path = "单轮_已完成_$STGNAME$/单轮_$STGNAME$_$DATE$_$TIME$"

batch_work_path = "批量_未完成_$STGNAME$"
batch_finished_path = "批量_已完成_$STGNAME$/批量_$STGNAME$_$BATCH-ID$/轮次_$ROUND-ID$"

rolling_work_path = "滚动_未完成_$STGNAME$"
rolling_finished_path = "滚动_已完成_$STGNAME$/滚动_$STGNAME$_$ROLLING-ID$/"

################################
# 炼丹基础参数
################################

# 用于贝叶斯选币炼丹的基础策略和所在的文件夹
base_stg_folder = "strategy"
base_stg_file = "Strategy_贝叶斯选币炼丹策略4"

# 用于保存贝叶斯选币炼丹结果的策略文件夹
output_stg_folder = "贝叶斯选币炼丹结果策略"

# 多任务执行炼丹的子任务数量，可以根据电脑内存配置修改，默认配置下 32G 内存可以跑 10 个
study_n_jobs = min(os.cpu_count() - 1, 25)
# 批量执行回测的子任务数量，内存大的话可以高一点提高性能，默认配置下 32G 内存可以跑 5 个
stg_back_test_n_jobs = min(os.cpu_count() - 1, 5)

# 炼丹使用的寻参采样方式，可选值：'贝叶斯'、'随机'
# 如果为 '贝叶斯'，则使用贝叶斯采样，主要用于寻找最优参数
# 如果为 '随机'，则使用随机采样，主要用于观察参数在均匀分布情况下的表现，用于代替参数平原遍历
study_sample_mode = '贝叶斯'

# 炼丹使用的样本内日期范围
sample_date_start = "2021-01-01"
sample_date_end = "2023-06-30"

# 炼丹的总轮数，通常 2000 到 10000 为合适的轮数
study_num_trials = 2000

# 炼丹检查点轮数，当迭代数达到该值时，会检查当前最高分是否大于 check_point_min_score 的值，
# 如果没有达到这个值则退出本轮执行，可用于批量炼丹时跳过效果不佳的轮次。
# 如果为 None 则不启用检查点功能。
check_point_trials = None
check_point_min_score = 100

# 使用 贝叶斯选币炼丹炉_批量.py 脚本执行批量炼丹的总次数
study_batch_num = 20

# 因子互斥模式，全局开关
# 针对设置为可选的因子，如果打开该模式，当同一个因子已经在过滤中使用，则不会再用于排序
factor_mutex_mode = False

################################
# 批量炼丹参数
################################

# 是否在批量炼丹时使用样本外回测评分加权模式
# 关闭后，批量炼丹时只使用样本内评分作为最终评分
use_back_test_mode = True

# 样本外回测使用的日期范围
# 如果起始日期为 None 将使用样本日期的结束日期
# 如果结束日期为 None 将使用最新的日期
back_test_date_start = None
back_test_date_end = None

# 批量炼丹时炼丹使用的样本内评分和回测使用的样本外评分求和使用的权重
study_batch_sample_score_pwr = 1
study_batch_back_test_score_pwr = 1

################################
# 炼丹输出策略文件参数
################################

# 炼丹完成自动生成的高分策略文件的个数
output_stg_top_num = 9
# 炼丹完成是否自动执行生成策略文件回测，以及回测策略的数量
output_stg_auto_back_test = True
output_stg_back_test_num = output_stg_top_num

# 批量炼丹时自动生成的高分策略文件的个数
# 每一轮次炼丹会回测前 output_stg_top_num_batch 名的测试，选择样本内外总分前 output_stg_top_num 名的策略
output_stg_top_num_batch = 20
# 批量炼丹完成自动执行生成策略文件回测的模式，以及回测策略的数量
# all：每一轮都回测，last：只在最后一轮结束时回测，None：不回测
output_stg_batch_back_test_mode = "last"
output_stg_batch_back_test_num = output_stg_top_num_batch

# 滚动炼丹时每个滚动窗口炼丹完成后，自动执行模拟实盘回测的策略文件个数，如果为 None 或 0 则不回测
# 回测结果保存在 滚动炼丹模拟实盘回测结果_XXX策略.csv 文件中
output_stg_top_num_rolling = None

# 滚动炼丹完成是否自动执行生成策略文件回测
output_stg_rolling_auto_back_test = True

################################
# 滚动炼丹参数
################################

# 滚动炼丹使用的最外层日期范围，格式为：2009-01-01
rolling_date_start = "2021-01-01"
rolling_date_end = None

# 滚动炼丹使用的最小和最大样本窗口月份数，如果最大值为 None 表示始终从 rolling_date_start 开始
# 默认为固定使用三年窗口期
rolling_sample_months_min = 12
rolling_sample_months_max = 12

# 滚动炼丹使用的样本外测试月份数
# 如果该参数配置为 0，则不分配样本外区间，也不执行样本外回测
rolling_back_test_months = 3
# 是否将样本外测试区间放在样本窗口的前面，如果为 True，则时间段顺序为 样本外 -> 样本内 -> 模拟实盘
rolling_back_test_in_front = False

# 滚动炼丹使用的模拟实盘交易月份数
# 实际滚动计算次数 = (rolling_date_end - (rolling_date_start + rolling_test_months)).month / rolling_trade_months
# 如果月份数太小，将需要更多的滚动计算次数
rolling_trade_months = 3

# 滚动炼丹输出策略使用的模板文件
rolling_template_file = "bys_rolling_template.py"

################################
# 数据文件相关参数
################################

# 学习任务的名称，一般不需要修改
study_name = "study_name"
# 用于保存 Optuna 学习数据库的文件名
study_storage_file = "内部数据.pkl"
# 用于保存炼丹结果，实现任务断点续传的文件，保存完整的炼丹数据
study_result_pkl = "学习结果.pkl"
# 用于保存炼丹结果的 csv 文件，只保存得分最高的前 study_result_top_num 个数据
study_result_csv = "学习结果.csv"
# 保存炼丹过程中出现高分记录的参数
study_result_txt = "高分数据.txt"
# 保存因子参数表数据
study_factor_params_csv = "因子参数.csv"
# 学习过程中的日志数据
study_log_file = os.path.join(data_path, "学习日志.log")
# 用于批量炼丹的 csv 文件
study_batch_csv = "批量学习结果_$STGNAME$.csv"
# 用于滚动炼丹的 csv 文件
study_rolling_csv = "滚动炼丹结果_$STGNAME$.csv"
# 如果 output_stg_top_num_rolling 有效，用于保存模拟实盘回测的文件名
study_rolling_back_test_csv = "滚动炼丹模拟实盘回测结果_$STGNAME$.csv"
# 在 学习结果.csv 中保存的高分数据的个数
study_result_top_num = 100
# 用于主任务向子任务传递参数的临时交换文件，如果为 None 则不使用
study_params_swap_pkl = "交换文件.pkl"
# 批量炼丹时的缓存文件，如果为 None 则不使用
study_batch_cache_pkl = "批量缓存.pkl"

################################
# 性能相关和其它参数
################################

# 安静模式下不输出 Optuna 自身的详细日志和调试日志
silent_mode = True
# 内存优化模式
reduce_mem_mode = True
# 使用 joblib 的共享内存机制节约内存（约10%-20%）
use_joblib_sharemem = True

# Linux 下使用 fork 提高性能，减少内存占用
use_fork = True

# 用于性能调试的选项
use_profiler = None  # 'study_task'

# 是否允许每天第一次运行时自动检查软件更新信息
daily_check_app_upgrade_info = True
# 用于保存软件更新检查信息的文件
app_upgrade_info_file = os.path.join(data_path, "更新信息.json")

############################################
# 用于贝叶斯炼丹的因子参数
############################################

# 因子数据中空值的最大比例
factor_null_value_rate_max = 0.2

# 在炼丹结果中保存的回测数据
save_rtn_cols = [
    "累积净值",
    "年化收益",
    "最大回撤",
    "年化收益/回撤比",
    "胜率",
    "盈亏收益比",
    "单周期最大盈利",
    "单周期大亏损",
    "最大连续盈利周期数",
    "最大连续亏损周期数",
    "每周期平均换手率",
    "收益率标准差",
]

# 公共的用户炼丹参数，如果策略文件中没有单独配置该参数，则使用此处的配置
# 炼丹炉根据下面的配置生成用户参数 bys_user_param_dict，可在策略文件的 before_filter 中使用
# 参数字典中 key 为参数名称，可以使用以下用 $ 定义的宏，一个参数中只允许一个宏：
#   $过滤因子$：指定该参数是跟每一个过滤因子相关的
#   $排序因子$：指定该参数是跟每一个排序因子相关的
# 以下参数只供演示，实际使用可根据需要调整
bys_user_param_dict = {
    "filter_rank": {
        "下限": 0.50,
        "上限": 0.95,
        "步长": 0.05,
    },
}

# 是否禁用过滤因子炼丹，如果禁用需要在基础策略文件中把 filter_list 放到自动生成代码区域的外面
bys_disable_filter = False

# 炼丹使用过滤因子数量的最小和最大值，None 表示不限制，如果策略文件中没有单独配置该参数，则使用此处的配置
# 如果使用多于 1 个因子，需要在策略文件中自己实现因子处理
bys_filter_factor_min_count = 1
bys_filter_factor_max_count = 1

# 是否禁用排序因子炼丹，如果禁用需要在基础策略文件中把 factor_list 放到自动生成代码区域的外面
bys_disable_sort = False

# 炼丹使用排序因子数量的最小和最大值，None 表示不限制，如果策略文件中没有单独配置该参数，则使用此处的配置
bys_sort_factor_min_count = 1
bys_sort_factor_max_count = 2

# 炼丹使用排序因子权重的最小和最大值，如果策略文件中没有单独配置该参数，则使用此处的配置
bys_sort_min_power = 1
bys_sort_max_power = 10

# 公共的用于回测结果评分的参数，如果策略文件中没有单独配置该参数，则使用此处的配置
# 以下参数只供演示，实际使用可根据需要调整
bys_eval_score_dict = {
    # 以下为回测结果 rtn 中的字段，可以针对每个字段指定自动计算评分的参数，使用简单的线性计算。
    # 单项最终分数为：(参数值 - 低值) / (高值 - 低值) * (高分 - 低分) + 低分
    # 比如年化收益为 100，计算单项得分为：(100 - 50) / (150 - 50) * (100 - 0) + 0 = 50.0
    # 所有项目得分加起来，作为最终的总分数，通过调整“高分”可以控制字段在最终得分的权重。
    # 如果定义了“附加”，表示使用更多的点做分段线性插值，附加数据格式为 [(x1, y1), (x2, y2), ...]，x 为指标值，y 得分
    # 如："年化收益": {"低分": 0, "低值": 50, "高分": 100, "高值": 200, "附加": [(80, 50), (150, 90), (500, 150), (2000, 200)]}
    # 如果指定了“低于低值惩罚”，则低于低值的样本，该项指标得分将加上惩罚分数（负数）
    # 如果指定了“高于高值惩罚”，则高于低值的样本，该项指标得分设置为惩罚分数（负数）
    # 可以定义的字段主要有：
    # 累积净值、年化收益、最大回撤、胜率、盈亏收益比、单周期最大盈利、单周期大亏损、最大连续盈利周期数、最大连续亏损周期数 等
    "中性": {
        "年化收益": {"低分": 0, "低值": 100, "高分": 50, "高值": 250},
        "年化收益/回撤比": {"低分": 0, "低值": 10, "高分": 50, "高值": 50},
        "胜率": {"低分": 0, "低值": 50, "高分": 10, "高值": 60},
    },
    "多头": {
        "年化收益": {"低分": 0, "低值": 200, "高分": 100, "高值": 500, "低于低值惩罚": -100},
        "年化收益/回撤比": {"低分": 0, "低值": 5, "高分": 50, "高值": 30},
        "胜率": {"低分": 0, "低值": 50, "高分": 20, "高值": 60},
    }
}

# 计算评分时是否要求每个单项指标的值都满足低值条件，如果为 True，则只要有一个指标不满足低值条件，则该样本不参与评分
# 该参数也可以指定为一个小于0的分数，比如 -1000，当指定为分数时，如果有一个指标不满足低值条件，则该指标的分数会加上配置的 -1000 分
bys_eval_score_check_low_limit = False

# 计算评分时是否要求每个单项指标的值都满足高值条件，如果为 True，则只要有一个指标不满足低值条件，则该样本不参与评分
# 该参数也可以指定为一个小于0的分数，比如 -1000，当指定为分数时，如果有一个指标不满足高值条件，则该指标的分数设置为 -1000 分
bys_eval_score_check_high_limit = False

# 样本外回测的评分参数，如果策略文件中没有单独配置该参数，则使用此处的配置
# 如果为 None 则与炼丹的评分参数一致
# 如果样本外周期较短或者评价侧重点不同，可以在此处重新定义，或者在策略文件中指定
bys_back_test_eval_score_dict = None

################################
# Optuna-dashboard 参数
################################

# 是否启用 Optuna-dashboard 面板，需要安装 optuna-dashboard 包
dashboard_enabled = False
# 是否自动打开 dashboard 页面，批量执行模式下只有执行第一轮时有效
dashboard_auto_open = True
# dashboard 服务监听的主机地址和端口
dashboard_host = "localhost"
dashboard_port = "8080"

#========================
# 企业微信配置
#========================

# 企业微信 key，如：12345678-485d-489e-a7c8-111111111111
weixin_key = ''
# 微信消息前缀
weixin_prefix = ''
weixin_proxies = {}

############################################
# 配置覆盖相关
############################################

# 使用额外的配置文件以修改默认参数，避免开发时修改Config.py文件误提交
_json_file = os.path.join(os.path.dirname(__file__), "bys_config.json")
if os.path.isfile(_json_file):
    with open(_json_file, mode="r", encoding="utf8") as f:
        import json

        json_config = json.load(f)
        for key in json_config:
            if key in globals():
                globals()[key] = json_config[key]

# 使用当前的配置定义默认参数
default_params = {}
for key in globals().copy():
    if (
        not key.startswith("_")
        and not key.startswith("app_")
        and not key in ["default_params"]
        and (
            globals()[key] is None
            or isinstance(globals()[key], (int, float, bool, str, dict, list))
        )
    ):
        default_params[key] = globals()[key]
